﻿// Type: D2L.Extensibility.AuthSdk.Impl.D2LUserContext
// Assembly: D2L.Extensibility.AuthSdk, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null
// MVID: B2FBA42B-591C-474F-A782-6FF215FEDFB9
// Assembly location: C:\Users\leandro.perdizo\Desktop\D2L.Extensibility.AuthSdk.dll

using D2L.Extensibility.AuthSdk;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Web;

namespace D2L.Extensibility.AuthSdk.Impl
{
  internal sealed class D2LUserContext : ID2LUserContext
  {
    private const string APP_ID_PARAMETER = "x_a";
    private const string USER_ID_PARAMETER = "x_b";
    private const string SIGNATURE_BY_APP_KEY_PARAMETER = "x_c";
    private const string SIGNATURE_BY_USER_KEY_PARAMETER = "x_d";
    private const string TIMESTAMP_PARAMETER = "x_t";
    private readonly string m_appId;
    private readonly string m_appKey;
    private readonly string m_userId;
    private readonly string m_userKey;
    private readonly HostSpec m_apiHost;
    private readonly ITimestampProvider m_timestampProvider;
    private long m_serverSkewMillis;

    long ID2LUserContext.ServerSkewMillis
    {
      get
      {
        return this.m_serverSkewMillis;
      }
      set
      {
        this.m_serverSkewMillis = value;
      }
    }

    internal D2LUserContext(ITimestampProvider timestampProvider, string appId, string appKey, string userId, string userKey, HostSpec apiHost)
    {
      this.m_appId = appId;
      this.m_appKey = appKey;
      this.m_userId = userId;
      this.m_userKey = userKey;
      this.m_apiHost = apiHost;
      this.m_timestampProvider = timestampProvider;
    }

    private Uri CreateApiUrl(string path)
    {
      UriBuilder uriBuilder = this.m_apiHost.ToUriBuilder();
      uriBuilder.Path = path;
      return uriBuilder.Uri;
    }

    public Uri CreateAuthenticatedUri(string path, string httpMethod)
    {
      return this.CreateAuthenticatedUri(this.CreateApiUrl(path), httpMethod);
    }

    public Uri CreateAuthenticatedUri(Uri fullUrl, string httpMethod)
    {
      string str = string.Join("&", Enumerable.Select<Tuple<string, string>, string>(this.CreateAuthenticatedTokens(fullUrl, httpMethod), (Func<Tuple<string, string>, string>) (token => token.Item1 + "=" + token.Item2)));
      if (fullUrl.Query != "")
        str = str + "&" + fullUrl.Query.Substring(1);
      return new UriBuilder(fullUrl)
      {
        Query = str
      }.Uri;
    }

    public IEnumerable<Tuple<string, string>> CreateAuthenticatedTokens(string path, string httpMethod)
    {
      return this.CreateAuthenticatedTokens(this.CreateApiUrl(path), httpMethod);
    }

    public IEnumerable<Tuple<string, string>> CreateAuthenticatedTokens(Uri fullUrl, string httpMethod)
    {
      long timestampInSeconds = this.GetAdjustedTimestampInSeconds();
      string data = D2LUserContext.FormatSignature(fullUrl.AbsolutePath, httpMethod, timestampInSeconds);
      Func<string, string, Tuple<string, string>> func = (Func<string, string, Tuple<string, string>>) ((s0, s1) => new Tuple<string, string>(s0, s1));
      List<Tuple<string, string>> list = new List<Tuple<string, string>>()
      {
        func("x_a", this.m_appId)
      };
      if (this.m_userId != null)
      {
        list.Add(func("x_b", this.m_userId));
        list.Add(func("x_d", D2LSigner.GetBase64HashString(this.m_userKey, data)));
      }
      list.Add(func("x_c", D2LSigner.GetBase64HashString(this.m_appKey, data)));
      list.Add(func("x_t", timestampInSeconds.ToString((IFormatProvider) CultureInfo.InvariantCulture)));
      return (IEnumerable<Tuple<string, string>>) list;
    }

    UserContextProperties ID2LUserContext.SaveUserContextProperties()
    {
      return new UserContextProperties()
      {
        UserId = this.m_userId,
        UserKey = this.m_userKey,
        Scheme = this.m_apiHost.Scheme,
        HostName = this.m_apiHost.Host,
        Port = this.m_apiHost.Port
      };
    }

    private bool TryCalculateServerSkewFromResponse(string responseBody)
    {
      long result;
      if (!new TimestampParser().TryParseTimestamp(responseBody, out result))
        return false;
      long timestampInMilliseconds = this.m_timestampProvider.GetCurrentTimestampInMilliseconds();
      this.m_serverSkewMillis = result * 1000L - timestampInMilliseconds;
      return true;
    }

    RequestResult ID2LUserContext.InterpretResult(D2LWebException exceptionWrapper)
    {
      switch (exceptionWrapper.StatusCode)
      {
        case HttpStatusCode.BadRequest:
          return RequestResult.BAD_REQUEST;
        case HttpStatusCode.Forbidden:
          return this.InterpretStatusCodeForbidden(exceptionWrapper.ResponseBody);
        case HttpStatusCode.NotFound:
          return RequestResult.NOT_FOUND;
        case HttpStatusCode.InternalServerError:
          return RequestResult.INTERNAL_SERVER_ERROR;
        default:
          return RequestResult.RESULT_UNKNOWN;
      }
    }

    private RequestResult InterpretStatusCodeForbidden(string responseBody)
    {
      if (this.TryCalculateServerSkewFromResponse(responseBody))
        return RequestResult.RESULT_INVALID_TIMESTAMP;
      return responseBody.Equals("Invalid token", StringComparison.InvariantCultureIgnoreCase) ? RequestResult.RESULT_INVALID_SIG : RequestResult.RESULT_NO_PERMISSION;
    }

    private long GetAdjustedTimestampInSeconds()
    {
      return (this.m_timestampProvider.GetCurrentTimestampInMilliseconds() + this.m_serverSkewMillis) / 1000L;
    }

    private static string FormatSignature(string path, string httpMethod, long timestampSeconds)
    {
      return string.Format("{0}&{1}&{2}", (object) httpMethod.ToUpperInvariant(), (object) HttpUtility.UrlDecode(path).ToLowerInvariant(), (object) timestampSeconds);
    }
  }
}
